﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using xKnight.Attacking.EncodedXss;
using xKnight.Attacking.SimpleXss;
using xKnight.Data;
using xKnight.Models;

namespace xKnight.Attacking
{
    internal abstract class Attacker
    {
        #region Events

        /// <summary>
        /// This event will rise just after all AttackAgents done their job
        /// </summary>
        public event AttackCompletedEventHandler AttackCompleted;

        /// <summary>
        /// This event will rise just after all AttackAgents get started
        /// </summary>
        public event AttackStartedEventHandler AttackStarted;

        /// <summary>
        /// this event will rise and report about AttackAgent`s inner status
        /// </summary>
        public event AttackAnnouncedEventHandler AttackAnnounced;

        #endregion

        #region Fields

        protected int _numberOfThreads; // number of AttackAgents
        protected Attack _attack;
        protected int _numberofAliveAgents; //
        protected object _lock = new object(); // used for managing shared resources in critical sections
        protected Host[] _hosts;
        protected AttackerAgent[] _attackerAgents;

        #endregion

        #region Properties

        public AttackerAgent[] AttackerAgents
        {
            get 
            {
                return _attackerAgents;
            }
        }

        #endregion

        #region Constructors

        protected Attacker(Attack attack, int numberOfThreads,Host[] hosts)
        {
            _attack = attack;
            _numberOfThreads = numberOfThreads;
            _hosts = hosts;

            CreateAttackerAgents(numberOfThreads);
        }

        #endregion

        #region Interface

        /// <summary>
        /// Inheritors must implement this method and create correct number of AttackAgents of
        /// appropriate type.
        /// </summary>
        public abstract void Attack();
        
        /// <summary>
        /// Inheritors must implement this mehtod. CreateAttackingSharedReource makes a
        /// shared resource which is shared among AttackerAgents. this resouce help Agents to sync
        /// their works and status
        /// </summary>
        /// <param name="inputs"></param>
        protected abstract AttackingSharedReource CreateAttackingSharedReource(params object[] inputs);

        protected virtual void StartAgents()
        {
            AttackerAgents.All(a =>
            {
                a.AttackAsync();
                return true;
            });
        }

        #endregion

        #region Event Handler

        /// <summary>
        /// Inheritors subscribe their agents AgentAttackAnnounced event to this method
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void AgentAttackAnnounced(object sender, AttackAnnouncedEventArgs e)
        {
            OnAttackAnnounced(e);
        }

        /// <summary>
        /// Inheritors subscribe their agents AgentAttackStarted event to this method
        /// </summary>
        protected void AgentAttackStarted(object sender, EventArgs e)
        {
            lock (_lock)
            {
                IncrementAliveAgents();
                if (AreAllAgentsStarted())
                {
                    OnAttackStarted(_attack.Id);
                }
            }
        }

        /// <summary>
        /// Inheritors subscribe their agents AgentAttackCompleted event to this method
        /// </summary>
        protected void AgentAttackCompleted(object sender, EventArgs e)
        {
            lock (_lock)
            {
                DecrementAliveAgents();
                if (!HasAliveAgent())
                {
                    _attack.FinishTime = DateTime.Now;
                    DataLayer.Save(_attack);

                    OnAttackCompleted(_attack.Id);
                }
            }
        }

        #endregion

        #region Events Raiser

        private void OnAttackCompleted(int attackId)
        {
            if (AttackCompleted != null)
            {
                AttackCompleted(this, new AttackCompletedEventArgs(attackId, DateTime.Now));
            }
        }

        private void OnAttackStarted(int attackId)
        {
            if (AttackStarted != null)
            {
                AttackStarted(this, new AttackStartedEventArgs(attackId, DateTime.Now));
            }
        }

        private void OnAttackAnnounced(AttackAnnouncedEventArgs e)
        {
            if (AttackAnnounced != null)
            {
                AttackAnnounced(this, new AttackAnnouncedEventArgs(e.AnnounceItem));
            }
        }

        #endregion

        #region Private Methods

        private bool HasAliveAgent()
        {
            return _numberofAliveAgents != 0;
        }

        private void DecrementAliveAgents()
        {
            _numberofAliveAgents--;
        }

        private bool AreAllAgentsStarted()
        {
            return _numberofAliveAgents == _numberOfThreads;
        }

        private void IncrementAliveAgents()
        {
            _numberofAliveAgents++;
        }

        private void CreateAttackerAgents(int numberOfThreads)
        {
            AttackingSharedReource sharedResource = CreateAttackingSharedReource();
            
            _attackerAgents=new AttackerAgent[_numberOfThreads];

            for (int i = 0; i < _numberOfThreads; i++)
            {
                AttackerAgent agent = AttackerAgentFactory.Create(sharedResource);

                agent.AgentAttackCompleted += AgentAttackCompleted;
                agent.AgentAttackStarted += AgentAttackStarted;
                agent.AgentAttackAnnounced += AgentAttackAnnounced;

                _attackerAgents[i] = agent;
            }
        }

        #endregion

    }
}
